using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace UCL.Powersource.Analysis
{

    

    //[DataContract]
    public interface IStatistic : ILabel
    {
        string Description { get; }
        string Name { get; }
        Guid Statistic_Guid { get; }
        string FormatString { get; }
        bool RequiresBuild { get; }

        IEnumerable<IStatisticResult> Results { get; }

        IReadOnlyDictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticTeamResult>> TeamResults { get; }

        void Resolve(ITfsTeamProjectCollectionConfiguration teamProjCollConfig, bool forceRecalculate = false);
        void Resolve(ITfsTeamProjectCollectionConfiguration teamProjCollConfig, ITimeFrame timeFrame, bool forceRecalculate = false);
        void Resolve(ITfsTeamProjectConfiguration teamProjConfig, bool forceRecalculate = false);
        void Resolve(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame, bool forceRecalculate = false);

        void ClearInvalidEntries(ITfsTeamProjectCollectionConfiguration teamProjCollConfig);
    }

    [DataContract(IsReference = true)]
    public abstract class StatisticTeamBase : IStatistic
    {
        public StatisticTeamBase()
        {
            _TeamResults = new Dictionary<ITfsTeamProjectConfiguration, Dictionary<ITimeFrame, StatisticTeamResult>>();
        }

        public abstract string Label { get; }

        public abstract string Description { get;  }

        public abstract string Name { get;  }

        public abstract Guid Statistic_Guid { get; }

        private string _FormatString = "0.00";

        public abstract bool RequiresBuild { get; }

        public virtual string FormatString
        {
            get { return _FormatString; }
        }

        public IEnumerable<IStatisticResult> Results
        {
            get
            {
                var l = new List<StatisticTeamResult>();
                foreach (var dictL in TeamResults.Values)
                {
                    l.AddRange(dictL.Values);
                }
                return l;
            }
        }

        [DataMember]
        private Dictionary<ITfsTeamProjectConfiguration, Dictionary<ITimeFrame, StatisticTeamResult>> _TeamResults;
        [IgnoreDataMember]
        private ILog _Log;
        [IgnoreDataMember]
        private ILog Log {
            get
            {
                return _Log ?? (_Log = LogManager.GetLog(this.GetType()));
            }
        }

        private Dictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticTeamResult>> _TeamResultsCache;
        public IReadOnlyDictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticTeamResult>> TeamResults
        {
            get
            {
                if (_TeamResultsCache == null)
                {
                    _TeamResultsCache = new Dictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticTeamResult>>();

                    foreach (var key in _TeamResults.Keys)
                    {
                        _TeamResultsCache.Add(key, _TeamResults[key]);
                    }
                }
                return _TeamResultsCache;
            }
        }

        public virtual void Resolve(ITfsTeamProjectCollectionConfiguration teamProjCollConfig, bool forceRecalculate = false)
        {
            foreach (var teamProjConfig in teamProjCollConfig)
            {
                Resolve(teamProjConfig, forceRecalculate);
            }
        }

        public virtual void Resolve(ITfsTeamProjectCollectionConfiguration teamProjCollConfig, ITimeFrame timeFrame, bool forceRecalculate = false)
        {
            foreach (var teamProjConfig in teamProjCollConfig)
            {
                Resolve(teamProjConfig, timeFrame, forceRecalculate);
            }
        }

        public virtual void Resolve(ITfsTeamProjectConfiguration teamProjConfig, bool forceRecalculate = false)
        {
            foreach (var timeFrame in teamProjConfig.ProjectTimeframes)
            {
                Resolve(teamProjConfig, timeFrame, forceRecalculate);
            }
        }

        public virtual void Resolve(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame, bool forceRecalculate = false)
        {
            if (!teamProjConfig.IsValidForStatistic) return; //If stat is not valid for statistic computation, don't do anything for it

            if (!_TeamResults.ContainsKey(teamProjConfig) || _TeamResults[teamProjConfig] == null) //If an entry for that stat does not exist yet, create it
                _TeamResults.Add(teamProjConfig, new Dictionary<ITimeFrame, StatisticTeamResult>());


            if (_TeamResults[teamProjConfig].ContainsKey(timeFrame) && _TeamResults[teamProjConfig][timeFrame] != null && !forceRecalculate) return;

            InvalidateTeamResults(); //Israel: I moved this method here

            double? value = null;

            try
            {
                value = CalculateFor(teamProjConfig, timeFrame);
            }
            catch (Exception exc)
            {
                Log.Warn(exc, "Stat {0} failed to calculate", Name);
            }

            var newResult = CreateResult(teamProjConfig, timeFrame, value);

            if (_TeamResults[teamProjConfig].ContainsKey(timeFrame))
                _TeamResults[teamProjConfig][timeFrame] = newResult;
            else
                _TeamResults[teamProjConfig].Add(timeFrame, newResult);

            //InvalidateTeamResults();
        }

        protected void InvalidateTeamResults()
        {
            _TeamResultsCache = null;
        }

        protected virtual StatisticTeamResult CreateResult(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame, double? value)
        {
            return new StatisticTeamResult(teamProjConfig, timeFrame, value);
        }

        protected abstract double? CalculateFor(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame);


        public void ClearInvalidEntries(ITfsTeamProjectCollectionConfiguration teamProjCollConfig)
        {
            List<ITfsTeamProjectConfiguration> projectsRemoved = new List<ITfsTeamProjectConfiguration>();
            List<ITimeFrame> timeframesRemoved = new List<ITimeFrame>();

            foreach (var proj in _TeamResults.Keys)
            {
                if (!teamProjCollConfig.Contains(proj))
                {
                    if (!projectsRemoved.Contains(proj)) projectsRemoved.Add(proj);
                    continue;
                }

                foreach (var timeFrame in _TeamResults[proj].Keys)
                {
                    if (!teamProjCollConfig.ProjectCollectionTimeFrames.Contains(timeFrame))
                    {
                        if (!timeframesRemoved.Contains(timeFrame)) timeframesRemoved.Add(timeFrame);
                        continue;
                    }
                }
            }

            foreach (var proj in projectsRemoved)
            {
                _TeamResults.Remove(proj);
            }

            foreach (var timeframe in timeframesRemoved)
            {
                foreach (var dict in _TeamResults.Values)
                {
                    if (dict.ContainsKey(timeframe)) dict.Remove(timeframe);
                }
            }

            InvalidateTeamResults();
        }
    }

    public interface IStatisticResult
    {
        ITimeFrame StatTimeFrame { get; }
        double? Value { get; }
    }

    [DataContract(IsReference = true)]
    public class StatisticTeamResult : IStatisticResult
    {
        public StatisticTeamResult(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame, double? value)
        {
            TeamProject = teamProjConfig;
            StatTimeFrame = timeFrame;
            Value = value;
        }

        [DataMember]
        public ITfsTeamProjectConfiguration TeamProject { get; private set; }

        [DataMember]
        public ITimeFrame StatTimeFrame { get; private set; }

        [DataMember]
        public virtual double? Value { get; private set; }
    }

    [DataContract(IsReference = true)]
    public abstract class StatisticCompareTeamsBase : IStatistic
    {
        public StatisticCompareTeamsBase()
        {
            _CompareTeamResults = new Dictionary<ITfsTeamProjectConfiguration, Dictionary<ITimeFrame, StatisticCompareTeamsResultCollection>>();
        }

        [ExcludeFromCodeCoverage]
        public abstract string Label { get; }
        [ExcludeFromCodeCoverage]
        public abstract string Description { get; }
        [ExcludeFromCodeCoverage]
        public abstract string Name { get; }
        [ExcludeFromCodeCoverage]
        public abstract Guid Statistic_Guid { get; }

        private string _FormatString = "0.00";
        [ExcludeFromCodeCoverage]
        public virtual string FormatString
        {
            get { return _FormatString; }
        }

        [IgnoreDataMember]
        private ILog _Log;
        [IgnoreDataMember]
        private ILog Log
        {
            get
            {
                return _Log ?? (_Log = LogManager.GetLog(this.GetType()));
            }
        }

        public IEnumerable<IStatisticResult> Results
        {
            get
            {
                var l = new List<StatisticTeamResult>();
                foreach (var dictL in _CompareTeamResults.Values)
                {
                    foreach (var list in dictL.Values)
                    {
                        l.AddRange(list);
                    }
                }
                return l;
            }
        }

        [DataMember]
        private Dictionary<ITfsTeamProjectConfiguration, Dictionary<ITimeFrame, StatisticCompareTeamsResultCollection>> _CompareTeamResults;


        private Dictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticCompareTeamsResultCollection>> _CompareTeamResultsCache;
        public IReadOnlyDictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticCompareTeamsResultCollection>> CompareTeamResults
        {
            get
            {
                if (_CompareTeamResultsCache == null)
                {
                    _CompareTeamResultsCache = new Dictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticCompareTeamsResultCollection>>();

                    foreach (var key in _CompareTeamResults.Keys)
                    {
                        _CompareTeamResultsCache.Add(key, _CompareTeamResults[key]);
                    }
                }
                return _CompareTeamResultsCache;
            }
        }


        private Dictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticTeamResult>> _TeamResultsCache;
        public IReadOnlyDictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticTeamResult>> TeamResults
        {
            get
            {
                if (_TeamResultsCache == null)
                {
                    _TeamResultsCache = new Dictionary<ITfsTeamProjectConfiguration, IReadOnlyDictionary<ITimeFrame, StatisticTeamResult>>();

                    foreach (var key in _CompareTeamResults.Keys)
                    {
                        var dict2 = new Dictionary<ITimeFrame, StatisticTeamResult>();

                        foreach (var key2 in _CompareTeamResults[key].Keys)
                        {
                            dict2.Add(key2, _CompareTeamResults[key][key2]);
                        }

                        _TeamResultsCache.Add(key, dict2);
                    }
                }
                return _TeamResultsCache;
            }
        }

        public void Resolve(ITfsTeamProjectCollectionConfiguration teamProjCollConfig, bool forceRecalculate = false)
        {
            foreach (var timeFrame in teamProjCollConfig.ProjectCollectionTimeFrames)
            {
                Resolve(teamProjCollConfig, timeFrame, forceRecalculate);
            }
        }

        public void Resolve(ITfsTeamProjectCollectionConfiguration teamProjCollConfig, ITimeFrame timeFrame, bool forceRecalculate = false)
        {
            foreach (var teamProjConfig in teamProjCollConfig)
            {
                Resolve(teamProjConfig, timeFrame, forceRecalculate);
            }
        }

        public void Resolve(ITfsTeamProjectConfiguration teamProjConfig, bool forceRecalculate = false)
        {
            foreach (var timeFrame in teamProjConfig.ProjectTimeframes)
            {
                Resolve(teamProjConfig, timeFrame, forceRecalculate);
            }
        }

        public void Resolve(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame, bool forceRecalculate = false)
        {
            foreach (var teamProjConfig2 in teamProjConfig.TeamProjectCollectionConfiguration)
            {
                Resolve(teamProjConfig, teamProjConfig2, timeFrame, forceRecalculate);
            }
        }

        public void Resolve(ITfsTeamProjectConfiguration teamProjConfig, ITfsTeamProjectConfiguration teamProjConfig2, ITimeFrame timeFrame, bool forceRecalculate = false)
        {
            if (teamProjConfig == teamProjConfig2) return;

            if (!(teamProjConfig.IsValidForStatistic && teamProjConfig2.IsValidForStatistic)) return;

            if (!_CompareTeamResults.ContainsKey(teamProjConfig))
                _CompareTeamResults.Add(teamProjConfig, new Dictionary<ITimeFrame, StatisticCompareTeamsResultCollection>());

            if (!_CompareTeamResults.ContainsKey(teamProjConfig2))
                _CompareTeamResults.Add(teamProjConfig2, new Dictionary<ITimeFrame, StatisticCompareTeamsResultCollection>());

            if (!_CompareTeamResults[teamProjConfig].ContainsKey(timeFrame))
                _CompareTeamResults[teamProjConfig].Add(timeFrame, new StatisticCompareTeamsResultCollection(teamProjConfig, timeFrame));

            if (!_CompareTeamResults[teamProjConfig2].ContainsKey(timeFrame))
                _CompareTeamResults[teamProjConfig2].Add(timeFrame, new StatisticCompareTeamsResultCollection(teamProjConfig2, timeFrame));

            var stat = _CompareTeamResults[teamProjConfig][timeFrame].FirstOrDefault(a =>
                                a.TeamProject == teamProjConfig2 && a.StatTimeFrame == timeFrame);

            if (stat != null)
            {
                if (!forceRecalculate) return;

                _CompareTeamResults[teamProjConfig][timeFrame].Remove(stat);
                stat = _CompareTeamResults[teamProjConfig2][timeFrame].FirstOrDefault(a =>
                                a.TeamProject == teamProjConfig && a.StatTimeFrame == timeFrame);
                _CompareTeamResults[teamProjConfig2][timeFrame].Remove(stat);
            }

            double? value = null;

            try
            {
                value = CalculateFor(teamProjConfig, timeFrame, teamProjConfig2, timeFrame);
            }
            catch (Exception exc)
            {
                Log.Warn(exc, "Stat {0} failed to calculate", Name);
            }

            var newStat = CreateResult(teamProjConfig2, timeFrame, value);
            _CompareTeamResults[teamProjConfig][timeFrame].Add(newStat);
            newStat = CreateResult(teamProjConfig, timeFrame, value);
            _CompareTeamResults[teamProjConfig2][timeFrame].Add(newStat);

            InvalidateTeamResults();

        }

        protected void InvalidateTeamResults()
        {
            _CompareTeamResultsCache = null;
            _TeamResultsCache = null;
        }

        protected virtual StatisticTeamResult CreateResult(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame, double? value)
        {
            return new StatisticTeamResult(teamProjConfig, timeFrame, value);
        }

        protected abstract double? CalculateFor(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame, ITfsTeamProjectConfiguration teamProjConfig2, ITimeFrame timeFrame2);

        public void ClearInvalidEntries(ITfsTeamProjectCollectionConfiguration teamProjCollConfig)
        {
            List<ITfsTeamProjectConfiguration> projectsRemoved = new List<ITfsTeamProjectConfiguration>();
            List<ITimeFrame> timeframesRemoved = new List<ITimeFrame>();
            List<StatisticTeamResult> resultsRemoved = new List<StatisticTeamResult>();

            foreach (var proj in _CompareTeamResults.Keys)
            {
                if (!teamProjCollConfig.Contains(proj))
                {
                    //_CompareTeamResults.Remove(proj);
                    if (!projectsRemoved.Contains(proj)) projectsRemoved.Add(proj);
                    continue;
                }

                foreach (var timeFrame in _CompareTeamResults[proj].Keys)
                {
                    if (!teamProjCollConfig.ProjectCollectionTimeFrames.Contains(timeFrame))
                    {
                        //_CompareTeamResults[proj].Remove(timeFrame);
                        if (!timeframesRemoved.Contains(timeFrame)) timeframesRemoved.Add(timeFrame);
                        continue;
                    }

                    foreach (var result in _CompareTeamResults[proj][timeFrame])
                    {
                        if (!teamProjCollConfig.Contains(result.TeamProject)
                            || !teamProjCollConfig.ProjectCollectionTimeFrames.Contains(result.StatTimeFrame))
                        {
                            //_CompareTeamResults[proj][timeFrame].Remove(result);
                            if (!resultsRemoved.Contains(result)) resultsRemoved.Add(result);
                            continue;
                        }
                    }
                }
            }

            foreach (var proj in projectsRemoved)
            {
                _CompareTeamResults.Remove(proj);
            }

            foreach (var timeframe in timeframesRemoved)
            {
                foreach (var dict in _CompareTeamResults.Values)
                {
                    if (dict.ContainsKey(timeframe)) dict.Remove(timeframe);
                }
            }

            foreach (var result in resultsRemoved)
            {
                foreach (var dict in _CompareTeamResults.Values)
                {
                    foreach (var collection in dict.Values)
                    {
                        if (collection.Contains(result)) collection.Remove(result);
                    }
                }
            }

            InvalidateTeamResults();
        }


        public abstract bool RequiresBuild { get; }
        
    }

    [DataContract(IsReference=true)]
    public class StatisticCompareTeamsResultCollection : StatisticTeamResult, IList<StatisticTeamResult>
    {
        public StatisticCompareTeamsResultCollection(ITfsTeamProjectConfiguration teamProjConfig, ITimeFrame timeFrame)
            : base(teamProjConfig, timeFrame, null)
        {
            _InnerResults = new List<StatisticTeamResult>();
        }

        [IgnoreDataMember]
        public override double? Value
        {
            get
            {
                var resList = new List<double>();

                foreach (var res in _InnerResults)
                {
                    if (res.Value != null) resList.Add((double)res.Value);
                }

                if (resList.Count > 0)
                    return resList.Average();

                return null;
            }
        }

        [DataMember]
        private List<StatisticTeamResult> _InnerResults;

        public int IndexOf(StatisticTeamResult item)
        {
            return _InnerResults.IndexOf(item);
        }

        public void Insert(int index, StatisticTeamResult item)
        {
            _InnerResults.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            _InnerResults.RemoveAt(index);
        }

        public StatisticTeamResult this[int index]
        {
            get
            {
                return _InnerResults[index];
            }
            set
            {
                _InnerResults[index] = value;
            }
        }

        public void Add(StatisticTeamResult item)
        {
            _InnerResults.Add(item);
        }

        public void Clear()
        {
            _InnerResults.Clear();
        }

        public bool Contains(StatisticTeamResult item)
        {
            return _InnerResults.Contains(item);
        }

        public void CopyTo(StatisticTeamResult[] array, int arrayIndex)
        {
            _InnerResults.CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return _InnerResults.Count; }
        }

        public bool Remove(StatisticTeamResult item)
        {
            return _InnerResults.Remove(item);
        }

        public IEnumerator<StatisticTeamResult> GetEnumerator()
        {
            return _InnerResults.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _InnerResults.GetEnumerator();
        }

        public bool IsReadOnly
        {
            get { return false; }
        }
    }
}
